﻿using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;

namespace DiscreteEventModel.Common {

    public class Cache<TKey, TItem> : Finalizer<TItem>
        where TKey : struct
        where TItem : Finalizable {

        public interface IContext {
            bool Read(TKey key, out TItem item);
            bool Write(TItem item, out TKey key);
            bool Remove(TKey key);
            void CancelAsyncWrite(TKey key, TItem item);
        }

        private IContext _context;
        private ConcurrentDictionary<TKey, WeakReference<TItem>> _activeItems;

        public Cache(IContext context) {
            this._context = context;
            this._activeItems = new ConcurrentDictionary<TKey, WeakReference<TItem>>();
        }

        protected override bool TryFinalize(TItem item) {
            TKey key;
            if(!this._context.Write(item, out key)) {
                return false;
            }

            WeakReference<TItem> weakRef;
            this._activeItems.TryRemove(key, out weakRef);
            return true;
        }

        public bool TryGet(TKey key, out TItem item) {
            WeakReference<TItem> weakRef;
            if(this._activeItems.TryGetValue(key, out weakRef)) {
                Debug.Assert(weakRef.TryGetTarget(out item));
                item.Finalizer = this;
                this._context.CancelAsyncWrite(key, item);
                return true;
            } else if(this._context.Read(key, out item)) {
                item.Finalizer = this;
                Debug.Assert(this._activeItems.TryAdd(key, new WeakReference<TItem>(item, true)));
                return true;
            }

            item = null;
            return false;
        }

        public void Put(TKey key, TItem newItem) {
            WeakReference<TItem> weakRef;
            if(this._activeItems.TryGetValue(key, out weakRef)) {
                TItem item;
                Debug.Assert(weakRef.TryGetTarget(out item));
                if(item != newItem) {
                    item.Finalizer = null;
                    this._context.CancelAsyncWrite(key, item);
                    newItem.Finalizer = this;
                    weakRef.SetTarget(newItem);
                }
            } else {
                newItem.Finalizer = this;
                Debug.Assert(this._activeItems.TryAdd(key, new WeakReference<TItem>(newItem, true)));
            }
        }

        public TItem this[TKey key] {
            get {
                TItem item;
                if(this.TryGet(key, out item)) {
                    return item;
                }
                return null;
            }
            set {
                if(value == null) {
                    this.Remove(key);
                } else {
                    this.Put(key, value);
                }
            }
        }

        public bool Remove(TKey key) {
            WeakReference<TItem> weakRef;
            if(this._activeItems.TryGetValue(key, out weakRef)) {
                TItem item;
                Debug.Assert(weakRef.TryGetTarget(out item));
                item.Finalizer = null;
                this._activeItems.TryRemove(key, out weakRef);
                this._context.CancelAsyncWrite(key, item);
            }
            return this._context.Remove(key);
        }

        public int Count {
            get {
                return this._activeItems.Count;
            }
        }
    }

}
